package com.flute.tools.data.berkeleydb;

import java.io.File;

import com.flute.tools.data.DataIterator;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

public class BerkeleyDatabase {

	protected Environment environment;
	protected EntryBinding<Object> keyBinding;
	protected EntryBinding<Object> valueBinding;
	protected Database database;
	protected Database classCatalogDB;
	protected StoredClassCatalog classCatalog;

	/**
	 * Construct a BerkeleyDatabase
	 * 
	 * @param env
	 * @param dbName
	 * @param keyBinding
	 * @param valueBinding
	 */
	public BerkeleyDatabase(String env, String dbName,
			EntryBinding<Object> keyBinding, EntryBinding<Object> valueBinding) {

		initial(dbName, env);

		DatabaseConfig dbConfig = new DatabaseConfig();
		dbConfig.setAllowCreate(true);
		classCatalogDB = environment.openDatabase(null, "classCatalog",
				dbConfig);
		classCatalog = new StoredClassCatalog(classCatalogDB);

		// 如果不设置对象转换方式则默认为SerialBinding
		if (null == keyBinding) {
			this.keyBinding = new SerialBinding<Object>(classCatalog,
					Object.class);
		} else {
			this.keyBinding = keyBinding;
		}

		if (null == valueBinding) {
			this.valueBinding = new SerialBinding<Object>(classCatalog,
					Object.class);
		} else {
			this.valueBinding = valueBinding;
		}

	}

	/**
	 * Construct a BerkeleyDatabase
	 * 
	 * @param env
	 *            环境名称 会形成一个存储BerkeleyDatabase数据的文件夹
	 * @param dbName
	 *            数据库名称
	 * @param keyBinding
	 */
	public BerkeleyDatabase(String env, String dbName,
			EntryBinding<Object> keyBinding) {

		initial(dbName, env);

		this.keyBinding = keyBinding;

		DatabaseConfig dbConfig = new DatabaseConfig();
		dbConfig.setAllowCreate(true);
		classCatalogDB = environment.openDatabase(null, "classCatalog",
				dbConfig);
		classCatalog = new StoredClassCatalog(classCatalogDB);

		this.valueBinding = new SerialBinding<Object>(classCatalog,
				Object.class);
	}

	private void initial(String dbName, String env) {

		EnvironmentConfig config = new EnvironmentConfig();
		config.setAllowCreate(true);
		File file = new File(env);
		if (!file.exists()) {
			file.mkdirs();
		}
		this.environment = new Environment(file, config);

		DatabaseConfig dbConfig = new DatabaseConfig();
		dbConfig.setAllowCreate(true);
		dbConfig.setDeferredWrite(true);

		this.database = environment.openDatabase(null, dbName, dbConfig);
	}

	public void close() {

		if (database != null) {
			database.close();
		}

		if (classCatalog != null) {
			classCatalog.close();
		}

		if (classCatalogDB != null) {
			classCatalogDB.close();
		}

		if (environment != null) {
			environment.close();
		}
	}

	public void push(Object object) {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			cursor = database.openCursor(null, null);
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry valueEntry = new DatabaseEntry();
			keyBinding.objectToEntry(object, keyEntry);
			valueBinding.objectToEntry(object, valueEntry);
			status = cursor.put(keyEntry, valueEntry);
			if (status != OperationStatus.SUCCESS) {
				if (cursor != null) {
					cursor.close();
				}
				throw new OperationException("fail to add object:" + object);
			}
		} catch (Exception e) {
			throw new OperationException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	public void push(Object objectKey, Object objectValue) {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			cursor = database.openCursor(null, null);
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry valueEntry = new DatabaseEntry();
			keyBinding.objectToEntry(objectKey, keyEntry);
			valueBinding.objectToEntry(objectValue, valueEntry);
			status = cursor.put(keyEntry, valueEntry);
			if (status != OperationStatus.SUCCESS) {
				if (cursor != null) {
					cursor.close();
				}
				throw new OperationException("fail to add object:"
						+ objectValue);
			}
		} catch (Exception e) {
			throw new OperationException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	public long size() {
		return database.count();
	}

	public synchronized Object pop() {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			DatabaseEntry key = new DatabaseEntry();
			DatabaseEntry value = new DatabaseEntry();
			cursor = database.openCursor(null, null);
			status = cursor.getFirst(key, value, LockMode.READ_UNCOMMITTED);

			if (status != OperationStatus.SUCCESS || key.getData() == null
					|| key.getData().length < 1 || value.getData() == null
					|| value.getData().length < 1) {
				return null;
			}
			cursor.delete();

			if (cursor != null) {
				cursor.close();
			}

			return valueBinding.entryToObject(value);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	public Object peek() {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			DatabaseEntry key = new DatabaseEntry();
			DatabaseEntry value = new DatabaseEntry();
			cursor = database.openCursor(null, null);
			status = cursor.getFirst(key, value, null);
			if (status != OperationStatus.SUCCESS || key.getData() == null
					|| key.getData().length < 1 || value.getData() == null
					|| value.getData().length < 1) {
				return null;
			}

			if (cursor != null) {
				cursor.close();
			}

			return valueBinding.entryToObject(value);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	public void remove(Object key) {
		try {
			DatabaseEntry keyEntry = new DatabaseEntry();
			keyBinding.objectToEntry(key, keyEntry);
			database.delete(null, keyEntry);
		} catch (Exception e) {
			throw new OperationException(e);
		}
	}

	public boolean contains(Object key) {
		if (key == null) {
			throw new RuntimeException("The key can't be null");
		}
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			cursor = database.openCursor(null, null);
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry valueEntry = new DatabaseEntry();
			keyBinding.objectToEntry(key, keyEntry);
			status = cursor.getSearchKey(keyEntry, valueEntry, null);
			if (status != OperationStatus.SUCCESS
					|| valueEntry.getData() == null
					|| valueEntry.getData().length <= 0) {
				return false;
			}
			return true;
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	public void printContent() {
		Cursor cursor = null;
		OperationStatus status = null;
		try {
			DatabaseEntry key = new DatabaseEntry();
			DatabaseEntry value = new DatabaseEntry();
			cursor = database.openCursor(null, null);
			status = cursor.getFirst(key, value, null);
			if (status == OperationStatus.SUCCESS) {
				System.out.println(keyBinding.entryToObject(key));
				System.out.println(valueBinding.entryToObject(value));
			}
			while (true) {
				status = cursor.getNext(key, value, LockMode.DEFAULT);
				if (status == OperationStatus.SUCCESS) {
					System.out.println(valueBinding.entryToObject(value));
				} else {
					break;
				}
			}

		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	public Object search(Object key) {
		if (key == null) {
			throw new RuntimeException("The key can't be null");
		}

		Cursor cursor = null;
		OperationStatus status = null;
		try {
			cursor = database.openCursor(null, null);
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry valueEntry = new DatabaseEntry();
			keyBinding.objectToEntry(key, keyEntry);
			status = cursor.getSearchKey(keyEntry, valueEntry, null);
			if (status != OperationStatus.SUCCESS
					|| valueEntry.getData() == null
					|| valueEntry.getData().length <= 0) {
				return null;
			}
			return valueBinding.entryToObject(valueEntry);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (cursor != null) {
				cursor.close();
			}
		}
	}

	public void flush() {
		database.sync();
	}

	public void clear() {
		this.environment.truncateDatabase(null, database.getDatabaseName(),
				true);
	}

	public DataIterator<Object> iterator(final Object key) {

		return new DataIterator<Object>() {
			private Cursor cursor;
			private DatabaseEntry keyEntry;
			private DatabaseEntry valueEntry;
			private boolean cursorOnHead;

			{
				init();
			}

			private void init() {
				try {
					cursor = database.openCursor(null, null);
					keyEntry = new DatabaseEntry();
					valueEntry = new DatabaseEntry();
					if (key != null) {
						cursorOnHead = false;
						keyBinding.objectToEntry(key, keyEntry);
						cursor.getSearchKey(keyEntry, valueEntry,
								LockMode.DEFAULT);
					} else {
						cursorOnHead = true;
					}
				} catch (Exception e) {
					close();
					throw new RuntimeException(e);
				}
			}

			@Override
			public void close() {
				if (cursor != null) {
					cursor.close();
				}
			}

			@Override
			public boolean hasNext() {
				try {
					boolean hasnext = true;
					if (!cursorOnHead) {
						OperationStatus status = cursor.getNext(keyEntry,
								valueEntry, LockMode.DEFAULT);
						if (status != OperationStatus.SUCCESS
								|| valueEntry.getData() == null
								|| valueEntry.getData().length <= 0) {
							hasnext = false;
						}
						cursor.getPrev(keyEntry, valueEntry, LockMode.DEFAULT);
						return hasnext;
					} else {
						OperationStatus status = cursor.getFirst(keyEntry,
								valueEntry, LockMode.DEFAULT);
						if (status != OperationStatus.SUCCESS
								|| valueEntry.getData() == null
								|| valueEntry.getData().length <= 0) {
							hasnext = false;
						}
						return hasnext;
					}
				} catch (Exception e) {
					close();
					throw new RuntimeException(e);
				}
			}

			@Override
			public Object next() {
				try {
					OperationStatus status;
					if (!cursorOnHead) {
						status = cursor.getNext(keyEntry, valueEntry,
								LockMode.DEFAULT);
					} else {
						status = cursor.getFirst(keyEntry, valueEntry,
								LockMode.DEFAULT);
						cursorOnHead = false;
					}
					if (status != OperationStatus.SUCCESS
							|| valueEntry.getData() == null
							|| valueEntry.getData().length <= 0) {
						return null;
					}
					return valueBinding.entryToObject(valueEntry);
				} catch (Exception e) {
					close();
					throw new RuntimeException(e);
				}
			}

			@Override
			public void reset() {
				init();
			}
		};
	}

}
